/*
 * This file is part of the Sx Framework Library.
 * 
 * Copyright (C) 2013 University of Colorado Denver
 * <min.choi@ucdenver.edu> <shane.transue@ucdenver.edu>
 * 
 * Permission is hereby granted, free of charge, to any person obtaining a copy 
 * of this software and associated documentation files (the "Software"), to deal
 * in the Software without restriction, including without limitation the rights
 * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 
 * copies of the Software, and to permit persons to whom the Software is 
 * furnished to do so, subject to the following conditions:
 * 
 * The above copyright notice and this permission notice shall be included in 
 * all copies or substantial portions of the Software.
 * 
 * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 
 * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 
 * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 
 * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 
 * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING 
 * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER 
 * DEALINGS IN THE SOFTWARE.
 */
#include <sxMassSpringSimulation.h>
#include <iostream>

Sx::Simulation::Simulations::MassSpringSimulation::MassSpringSimulation() : SoftBodySimulation() {
	this->paused = false;
	this->collisionModel = nullptr;
}

Sx::Simulation::Simulations::MassSpringSimulation::MassSpringSimulation(Physics::Collision::AbstractCollisionModel<Objects::MassSpringMesh>* collisionModel) {
	this->paused = false;
	this->collisionModel = collisionModel;
}

Sx::Simulation::Simulations::MassSpringSimulation::~MassSpringSimulation() {
	this->meshes.clear();
	delete this->collisionModel;
}

bool Sx::Simulation::Simulations::MassSpringSimulation::construct() {
	if ( this->collisionModel == nullptr ) {
		std::cerr << "[MassSpringSimulation:construct] Warning: Mass-Spring simulation does not contain a collision model." << std::endl;
		std::cerr << "  Collision will not be enabled for this simulation." << std::endl;
	}

	bool success = false;
	success = this->collisionModel->construct(this->meshes);

	//--------------------------------------------------------------------------
	// Notify Listeners about construct Event
	//--------------------------------------------------------------------------
	AbstractSimulation::construct();
	return success;
}

void Sx::Simulation::Simulations::MassSpringSimulation::update() {
	if ( this->paused == true ) return;

	//--------------------------------------------------------------------------
	// Notify Listeners about preUpdate Event
	//--------------------------------------------------------------------------
	AbstractSimulation::preUpdate();

	float elapsed;
	if ( this->timeStepType == REALTIME ) this->timer.stop(elapsed);

	//--------------------------------------------------------------------------
	// Update all of the objects in the simulation.
	//--------------------------------------------------------------------------
	for ( unsigned int i = 0; i < this->meshes.size(); i++ ) {
		if ( this->timeStepType == FIXED ) this->meshes[i]->update(this->timeStep);
		else if ( this->timeStepType == REALTIME ) this->meshes[i]->update(elapsed);
		else return;
	}

	//--------------------------------------------------------------------------
	// If a collision model is assigned, then handle the collisions that may
	// have occurred during this update.
	//--------------------------------------------------------------------------
	if ( this->collisionModel != nullptr ) {
		this->collisionModel->update(this->meshes);
		if ( this->timeStepType == FIXED )
			this->collisionModel->resolveCollisions(this->meshes, this->timeStep);
		else if ( this->timeStepType == REALTIME )
			this->collisionModel->resolveCollisions(this->meshes, elapsed);
		else return;
	}


	//--------------------------------------------------------------------------
	// Notify Listeners about postUpdate Event
	//--------------------------------------------------------------------------
	AbstractSimulation::postUpdate();

	if ( this->timeStepType == REALTIME ) this->timer.start();
}

void Sx::Simulation::Simulations::MassSpringSimulation::render() const {
	//--------------------------------------------------------------------------
	// Notify Listeners about preRender Event
	//--------------------------------------------------------------------------
	AbstractSimulation::preRender();

	//--------------------------------------------------------------------------
	// Render the mass-spring meshes in this simulation.
	//--------------------------------------------------------------------------
	for ( unsigned int i = 0; i < this->meshes.size(); i++ )
		this->meshes[i]->render();

	//--------------------------------------------------------------------------
	// Notify Listeners about postRender Event
	//--------------------------------------------------------------------------
	AbstractSimulation::postRender();
}

void Sx::Simulation::Simulations::MassSpringSimulation::reset() {
	for ( unsigned int i = 0; i < this->meshes.size(); i++ ) {
		if ( this->meshes[i] == nullptr ) {
			std::cerr << "[MassSpringSimulation:reset] Error: Simulation contains a nullptr mass-spring mesh." << std::endl;
			std::cerr << "  Could not reset the nullptr mesh." << std::endl;
			continue;
		}

		this->meshes[i]->reset();
	}

	//--------------------------------------------------------------------------
	// Notify Listeners about the reset after the changes have been applied.
	//--------------------------------------------------------------------------
	AbstractSimulation::reset();
}

bool Sx::Simulation::Simulations::MassSpringSimulation::add(const std::shared_ptr<MassSpringMesh>& mesh) {
	if ( mesh == nullptr ) {
		std::cerr << "[MassSpringSimulation:add] Error: Cannot add nullptr mesh." << std::endl;
		std::cerr << "  The provided mesh was not added to the simulation." << std::endl;
		return false;
	}

	this->meshes.add(mesh);
	if ( this->collisionModel != nullptr ) return this->collisionModel->construct(this->meshes);
	return true;
}

bool Sx::Simulation::Simulations::MassSpringSimulation::remove(const std::shared_ptr<MassSpringMesh>& mesh) {
	if ( mesh == nullptr ) {
		std::cerr << "[MassSpringSimulation:remove] Error: Cannot add nullptr mesh." << std::endl;
		std::cerr << "  The provided mesh could not be removed." << std::endl;
		return false;
	}

	bool success = this->meshes.remove(mesh);
	if ( success == true ) {
		if ( this->collisionModel != nullptr ) return this->collisionModel->construct(this->meshes);
	}
	else return false;
	return false;
}

bool Sx::Simulation::Simulations::MassSpringSimulation::removeAt(unsigned int index) {
	if ( index >= this->meshes.size() ) {
		std::cerr << "[MassSpringSimulation:removeAt] Error: Index: " << index << " out of bounds." << std::endl;
		std::cerr << "  No mesh removed." << std::endl;
		return false;
	}

	bool success = this->meshes.removeAt(index);
	if ( success == true ) {
		if ( this->collisionModel != nullptr ) return this->collisionModel->construct(this->meshes);
	}
	else return false;
	return false;
}

unsigned int Sx::Simulation::Simulations::MassSpringSimulation::getMeshCount() const {
	return this->meshes.size();
}

bool Sx::Simulation::Simulations::MassSpringSimulation::zeroAllExternalForces() {
	for ( unsigned int i = 0; i < this->meshes.size(); i++ ) {
		this->meshes[i]->zeroExternalForces();
	}
	return true;
}

bool Sx::Simulation::Simulations::MassSpringSimulation::zeroAllCollisions() {
	if ( this->collisionModel == nullptr ) return false;
	this->collisionModel->reset(this->meshes);
	return true;
}

std::shared_ptr<Sx::Simulation::Objects::MassSpringMesh> const Sx::Simulation::Simulations::MassSpringSimulation::getMesh(unsigned int index) const {
	if ( index >= this->meshes.size() ) {
		std::cerr << "[MassSpringSimulation:getMesh] Error: Index: " << index << " out of bounds." << std::endl;
		std::cerr << "  Returning nullptr." << std::endl;
		return nullptr;
	}

	return this->meshes[index];
}
